<?php

namespace myAnnotation;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\FileCacheReader;
use myAnnotation\Annotation\Autowire;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Parser;
use PhpParser\ParserFactory;

class AnnotationScan {
    /**
     * @var $annotationReader FileCacheReader|AnnotationReader
     */
    protected $annotationReader;

    /**
     * @var Parser
     */
    protected $parser;

    protected $proxy = [];

    /**
     * 注解读取白名单
     *
     * @var array
     */
    protected $whitelist = [
        "author",
        "var",
        "after",
        "afterClass",
        "backupGlobals",
        "backupStaticAttributes",
        "before",
        "beforeClass",
        "codeCoverageIgnore*",
        "covers",
        "coversDefaultClass",
        "coversNothing",
        "dataProvider",
        "depends",
        "doesNotPerformAssertions",
        "expectedException",
        "expectedExceptionCode",
        "expectedExceptionMessage",
        "expectedExceptionMessageRegExp",
        "group",
        "large",
        "medium",
        "preserveGlobalState",
        "requires",
        "runTestsInSeparateProcesses",
        "runInSeparateProcess",
        "small",
        "test",
        "testdox",
        "testWith",
        "ticket",
        "uses"
    ];

    public function __construct() {
        $this->init();
    }

    protected function init() {
        AnnotationRegistry::registerLoader('class_exists');
//        AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Route.php'); //注册文件
//        AnnotationRegistry::registerAutoloadNamespace('Faily\\Annotation'); //注册命名空间
//        AnnotationRegistry::registerAutoloadNamespaces(['Faily\\Annotation' => null]); //注册多个命名空间
        // 注解读取白名单
        $this->setWhiteList();
        // 注解读取器
        $this->annotationReader = config('system.annotation.cache')
            ?
            new FileCacheReader(new AnnotationReader(), env('runtime_path') . DIRECTORY_SEPARATOR . "annotation", true)
            :
            new AnnotationReader();
        $this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP5);
    }

    /**
     * 读取类的所有属性的注解
     *
     * @param $instance
     *
     * @throws \ReflectionException
     */
    public function readPropertiesAnnotation($instance) {
        $reflectionClass = new \ReflectionClass($instance);
        $reflectionProperties = $reflectionClass->getProperties();
        foreach ($reflectionProperties as $reflectionProperty) {
            $propertyAnnotations = $this->annotationReader->getPropertyAnnotations($reflectionProperty);
            foreach ($propertyAnnotations as $propertyAnnotation) {
                if ($propertyAnnotation instanceof Autowire) {
                    if ($reflectionProperty->isPublic() && !$reflectionProperty->isStatic()) {
                        if ($class = $this->getPropertyType($reflectionClass->getFileName(), $reflectionClass->getNamespaceName(), $reflectionProperty->getDocComment())) {
                            if (isset($this->proxy[$class])) {
                                $reflectionProperty->setValue($instance, $this->proxy[$class]);
                            } else {
                                $proxyObj = new Proxy(app($class));
                                $this->proxy[$class] = $proxyObj;
                                $reflectionProperty->setValue($instance, $proxyObj);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 读取当前方法的注解 如果有拦截器注解则返回拦截器注解对象数组
     *
     * @param $instance
     * @param $method
     *
     * @return array
     * @throws \ReflectionException*@throws \Exception
     */
    public function readMethodAnnotation($instance, $method) {
        $reflectionMethod = new \ReflectionMethod($instance, $method);
        $methodAnnotations = $this->annotationReader->getMethodAnnotations($reflectionMethod);
        $interceptorEnable = config('?system.annotation.interceptor.enable') ? config('system.annotation.interceptor.enable') : true;
        $interceptorWhitelist = config('?system.annotation.interceptor.whitelist') ? config('system.annotation.interceptor.whitelist') : [];
        $validateErrorMsgCallback = config('?system.annotation.validate.callback') ? config('system.annotation.validate.callback') : [];
        $interceptorAnnotationObjs = [];
        foreach ($methodAnnotations as $methodAnnotation) {

            if (!$methodAnnotation instanceof AnnotationInterceptor) {
                if (class_exists($methodAnnotation->handle)) {
                    call_user_func_array([
                                             $methodAnnotation->handle,
                                             'run'
                                         ], [$methodAnnotation]);
                }
            } else {
                if ($interceptorEnable && !in_array(get_class($methodAnnotation), $interceptorWhitelist)) {
                    $interceptorAnnotationObjs[] = $methodAnnotation;
                }
            }
        }

        return $interceptorAnnotationObjs;
    }

    /**
     * 设置注解读取白名单
     *
     * @return array
     */
    protected function setWhiteList() {
        if ($whitelist = config('system.annotation.whitelist')) {
            $this->whitelist = array_merge($this->whitelist, $whitelist);
        }
        foreach ($this->whitelist as $v) {
            AnnotationReader::addGlobalIgnoredName($v);
        }
    }

    /**
     * 格式化错误信息
     *
     * @param string $msg
     *
     * @return false|string
     */
    protected function formatErrorMsg($msg = '') {
        return json_encode([
                               'code' => 422,
                               'data' => '',
                               'msg'  => $msg,
                               'time' => request()->time()
                           ]);
    }


    /**
     * 获取属性声明的var类型
     *
     * @param $filepath
     * @param $namespace
     * @param $doc
     *
     * @return bool|mixed|string
     */
    protected function getPropertyType($filepath, $namespace, $doc) {
        if (!preg_match('/@var\s+(\S+)\s+/', $doc, $matches)) {
            return false;
        }
        $type = $matches[1];
        if (!(strpos($type, '\\') === 0)) {
            $stmts = $this->parser->parse(file_get_contents($filepath));
            $uses = $this->getUses($stmts);
            if (array_key_exists($type, $uses)) {
                $type = $uses[$type];
            } else {
                $type = $namespace . '\\' . $type;
            }
        }

        return $type;
    }

    /**
     * 获取类中的use信息
     *
     * @param $stmts
     *
     * @return array
     */
    protected function getUses($stmts) {
        $uses = [];
        foreach ($stmts as $v) {
            if ($v instanceof Namespace_) {
                foreach ($v->stmts as $vv) {
                    if ($vv instanceof Use_) {
                        foreach ($vv->uses as $vvv) {
                            $name = implode('\\', $vvv->name->parts);
                            if ($vvv->alias) {
                                $alias = $vvv->alias;
                            } else {
                                $pos = strrpos($name, '\\');
                                $alias = substr($name, $pos ? $pos + 1 : 0);
                            }
                            $uses[$alias] = $name;
                        }
                    }
                }
            }
        }

        return $uses;
    }
}
